﻿using Microsoft.AspNetCore.Mvc.Rendering;
using PMS.Core.Caching;
using PMS.Core.Configuration.CommonEnumConfigs;
using PMS.Data.Entities.Customers;
using PMS.Data.Entities.NaviMenus;
using PMS.Data.Entities.Settings;
using PMS.Services.Caching;
using PMS.Services.EntityServices;
using PMS.Services.EntityServices.Customers;
using PMS.Services.EntityServices.Localization;
using PMS.Services.EntityServices.Medias;
using PMS.Services.EntityServices.NaviMenus;
using PMS.Services.EntityServices.Stores;
using PMS.Web.Factories.Customers;
using PMS.Web.Factories.Stores;
using PMS.Web.Framework.Models;
using PMS.Web.Framework.ModelUtils;
using PMS.Web.Models;
using PMS.Web.Models.NaviMenus;
using PMS.Web.Models.Stores;
using System;
using System.Collections.Generic;
using System.Linq;

namespace PMS.Web.Factories.NaviMenus
{
    public partial class NaviMenuModelFactory : INaviMenuModelFactory
    {
        #region Fields

        private readonly CatalogSettings _catalogSettings;
        private readonly IStoreService _storeService;
        private readonly IMediaProvider _mediaProvider;
        private readonly ILanguageService _languageService;
        private readonly INaviMenuService _naviMenuService;
        private readonly ICacheKeyService _cacheKeyService;
        private readonly IStaticCacheManager _staticCacheManager;
        private readonly IAclSupportedModelFactory _aclSupportedModelFactory;
        private readonly IStoreMappingSupportedModelFactory _storeMappingSupportedModelFactory;

        #endregion

        #region Ctor

        public NaviMenuModelFactory(CatalogSettings catalogSettings,
            IStoreService storeService,
            IMediaProvider mediaProvider,
            ILanguageService languageService,
            INaviMenuService naviMenuService,
            ICacheKeyService cacheKeyService,
            IStaticCacheManager staticCacheManager,
            IAclSupportedModelFactory aclSupportedModelFactory,
            IStoreMappingSupportedModelFactory storeMappingSupportedModelFactory)
        {
            _catalogSettings = catalogSettings;
            _storeService = storeService;
            _mediaProvider = mediaProvider;
            _languageService = languageService;
            _naviMenuService = naviMenuService;
            _cacheKeyService = cacheKeyService;
            _staticCacheManager = staticCacheManager;
            _aclSupportedModelFactory = aclSupportedModelFactory;
            _storeMappingSupportedModelFactory = storeMappingSupportedModelFactory;
        }

        #endregion

        #region Utilities
        /// <summary>
        /// Prepare available stores
        /// </summary>
        private NaviMenuSearchModel PrepareStores(NaviMenuSearchModel searchModel, Customer curLoginCustomer)
        {
            if (searchModel == null)
                throw new ArgumentNullException(nameof(searchModel));
            if(curLoginCustomer == null)
                throw new ArgumentNullException(nameof(curLoginCustomer));

            //prepare available stores
            //All CustomersAdmins can visit Public NaviMenus(NaviMenu.LimitedToStores = 0)
            var availableStores = _storeService.GetAllStores();
            //IgnoreStoreLimitations setting true: All Select item disabled for choose
            //currentLoginCustomer is not Administrator: Only registed store can be chosen
            bool isAdministrator = curLoginCustomer.IsAdministrator();
            foreach (var store in availableStores)
            {
                var item = new StoreSelectModel()
                {
                    Id = store.Id,
                    Name = store.Name,
                    Url = store.Url,
                    Order = store.DisplayOrder,
                    Selected = false,
                    Disabled = true
                };
                if(_catalogSettings.IgnoreStoreLimitations)
                    item.Disabled = true;
                else
                {
                    //系统管理员拥有最高权限，可以查看所有店铺导航菜单
                    if (isAdministrator)
                        item.Disabled = false;
                    //其他管理员只能查看本店铺导航菜单及公共导航菜单 Menu.LimitedToStores = 0
                    else if (!isAdministrator && curLoginCustomer.RegisteredInStoreId == store.Id)
                        item.Disabled = false;
                    else
                        item.Disabled = true;
                }
                searchModel.AvailableStores.Add(item);
            }
            
            searchModel.AvailableStores.Insert(0, new StoreSelectModel
            {
                Id = 0,
                Name = "店铺公用",
                Url = "",
                Order = 1,
                Selected = true,
                Disabled = false
            });
            //Set Default search
            searchModel.SearchStoreId = 0;

            return searchModel;
        }

        /// <summary>
        /// Prepare available Navimenu templates
        /// </summary>
        /// <param name="items">Navimenu template items</param>
        private NaviMenuEditModel PrepareNaviMenuTemplates(NaviMenuEditModel model)
        {
            if (model == null)
                throw new ArgumentNullException(nameof(model));

            if(model.Id > 0)
            {
                var mappingTemplates = _naviMenuService.GetNaviMenuLayoutTemplatesByNaviMenuId(model.Id);
                if(mappingTemplates != null && mappingTemplates.Count > 0)
                {
                    foreach(int tid in mappingTemplates.Select(mp => mp.Id).ToList())
                    {
                        model.SelectedTemplateIds.Add(tid);
                    }
                }
            }
            //prepare available category templates
            var availableTemplates = _naviMenuService.GetAllNaviMenuLayoutTemplates()
                    .GroupBy(tpl => tpl.TemplateType)
                    .Select(tpl => tpl);
            foreach (var grpTpl in availableTemplates)
            {
                var grpItem = new LayoutTemplateSelectModel
                {
                    Name = grpTpl.Key.GetDescription(true),
                    Id = Convert.ToInt32(grpTpl.Key),
                    TemplateType = grpTpl.Key,
                    Children = new List<LayoutTemplateSelectModel>()
                };
                foreach(var template in grpTpl.ToList())
                {
                    var optItem = new LayoutTemplateSelectModel
                    {
                        Id = template.Id,
                        Name = template.TemplateDescription,
                        TemplatePreviewImageUrl = string.Empty,
                        TemplateType = template.TemplateType,
                        Children = null,
                        Order = template.DisplayOrder,
                        Selected = model.SelectedTemplateIds.Contains(template.Id), //false,
                        Disabled = false
                    };
                    if (template.TemplatePreviewImageId > 0)
                    {
                        var previewImg = _mediaProvider.MediaService.GetMediaFileUrl(template.TemplatePreviewImageId);
                        optItem.TemplatePreviewImageUrl = previewImg.IsDefault
                            ? string.Empty
                            : previewImg.FileUrl;
                    }

                    grpItem.Children.Add(optItem);
                }
                model.AvailableLayoutTemplates.Add(grpItem);                   
            }
            return model;
        }

        /// <summary>
        /// Prepare available categories
        /// </summary>
        /// <param name="items">Category items</param>
        /// <param name="withSpecialDefaultItem">Whether to insert the first special item for the default value</param>
        /// <param name="defaultItemText">Default item text; pass null to use default value of the default item text</param>
        private NaviMenuEditModel PrepareParentMenuList(NaviMenuEditModel model)
        {
            if (model == null)
                throw new ArgumentNullException(nameof(model));

            //prepare available categories
            var availableMenuItems = GetNaviMenuList(group: model.MenuGroup);
            foreach (var menuItem in availableMenuItems)
            {
                if(model.ParentId > 0)
                {
                    menuItem.Selected = model.ParentId.ToString() == menuItem.Value;
                }
                model.AvailableParentMenus.Add(menuItem);
            }

            //insert special item for the default value
            model.AvailableParentMenus.Insert(0, new SelectListItem
            {
                Value = "0",
                Text = " ---- ",
                Selected = model.ParentId == 0
            });
            return model;
        }

        private void PrepareLanguages(IList<SelectListItem> items, string defaultlanCode)
        {
            if (items == null)
                throw new ArgumentNullException(nameof(items));
            //prepare "Languages"
            var availableLangs = _languageService.GetAllLanguages();
            foreach (var lan in availableLangs)
            {
                items.Add(new SelectListItem
                {
                    Value = lan.LanguageCode,
                    Text = lan.DisplayName,
                    Selected = defaultlanCode.Equals(lan.LanguageCode, StringComparison.InvariantCultureIgnoreCase)
                });
            }
        }
        /// <summary>
        /// Get category list
        /// </summary>
        /// <param name="showHidden">A value indicating whether to show hidden records</param>
        /// <returns>Category list</returns>
        protected virtual List<SelectListItem> GetNaviMenuList(bool showHidden = true, MenuGroups group = MenuGroups.Articles)
        {
            var cacheKey = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.UINaviMenuListKey, showHidden, (int)group);
            var listItems = _staticCacheManager.Get(cacheKey, () =>
            {
                var menus = _naviMenuService.GetAllNaviMenusByMenuGroup(group, 0, showHidden);
                var allLangs = _languageService.GetAllLanguages();
                var groupMenus = new List<SelectListItem>();
                foreach(var lan in allLangs)
                {
                    var lanGroup = new SelectListGroup() { Name = lan.DisplayName };
                    var lanMenus = menus.Where(m => m.LanguageCode.Equals(lan.LanguageCode, StringComparison.CurrentCultureIgnoreCase)).ToList();
                    foreach(var mu in lanMenus)
                    {
                        groupMenus.Add(new SelectListItem
                        {
                            Text = _naviMenuService.GetFormattedBreadCrumb(mu, menus),
                            Value = mu.Id.ToString(),
                            Group = lanGroup
                        });
                    }
                }

                return groupMenus;
            });

            var result = new List<SelectListItem>();
            //clone the list to ensure that "selected" property is not set
            foreach (var item in listItems)
            {
                result.Add(new SelectListItem
                {
                    Text = item.Text,
                    Value = item.Value,
                    Group = item.Group
                });
            }

            return result;
        }

        private MenuGroups ConvertMenuGroupIdToMenuGroup(int menugrpId)
        {
            var menugrpEntity = _naviMenuService.GetNaviMenuGroupById(menugrpId);
            if (menugrpEntity == null) return MenuGroups.Articles;

            try
            {
                foreach (string enumName in Enum.GetNames(typeof(MenuGroups)))
                {
                    if (menugrpEntity.DisplayName.IndexOf(enumName) > 0)
                        return (MenuGroups)Enum.Parse(typeof(MenuGroups), enumName);
                }
            } 
            catch(Exception)
            {
                return MenuGroups.Articles;
            }
            return MenuGroups.Articles;
        }
        #endregion


        #region Methods
        /// <summary>
        /// Prepare NaviMenu search model
        /// </summary>
        /// <param name="searchModel">NaviMenuSearchModel search model</param>
        /// <param name="currentCustomer">workContext for current LoginCustomer and current Language</param>
        /// <returns>NaviMenuSearchModel</returns>
        public virtual NaviMenuSearchModel PrepareNaviMenuSearchModel(NaviMenuSearchModel searchModel, Customer currentCustomer)
        {
            if (searchModel == null)
                throw new ArgumentNullException(nameof(searchModel));
            if (currentCustomer == null)
                throw new ArgumentNullException(nameof(currentCustomer));

            //prepare available stores
            searchModel = PrepareStores(searchModel, currentCustomer);

            //prepare "Languages"
            PrepareLanguages(searchModel.AvailableLanguages, searchModel.LanguageCode);
                                  
            //prepare page parameters
            searchModel.SetGridPageSize();

            return searchModel;
        }

        /// <summary>
        /// Prepare NaviMenuEdit model
        /// </summary>
        /// <param name="model">NaviMenuEditModel</param>
        /// <param name="entity">NaviMenu</param>
        /// <param name="excludeProperties">Whether to exclude populating of some properties of model</param>
        /// <returns>NaviMenuEditModel model</returns>
        public virtual NaviMenuEditModel PrepareNaviMenuEditModel(NaviMenuEditModel model, NaviMenu entity, bool excludeProperties = false)
        {
            if (entity != null)
            {
                //fill in model values from the entity
                if (model == null)
                {
                    model = entity.ToModel<NaviMenuEditModel>();
                    model.MenuGroup = ConvertMenuGroupIdToMenuGroup(entity.MenuGroupId);
                }
            }

            //set default values for the new model
            if (entity == null)
            {
                model.PageSize = _catalogSettings.DefaultCategoryPageSize;
                model.PageSizeOptions = _catalogSettings.DefaultCategoryPageSizeOptions;
                model.IsActive = true;
                model.ShowOnHomepage = true;
                model.BeautifyUrlRoute = false;
                model.UrlTarget = LinkOpenMode.PageRedirect;
                model.Id = model.ParentId = 0;
                if (string.IsNullOrEmpty(model.LanguageCode))
                    model.LanguageCode = _languageService.GetAllLanguages().First().LanguageCode;
                if (Convert.ToInt32(model.MenuGroup) <= 0)
                    model.MenuGroup = MenuGroups.Articles;
                model.LayoutTemplateType = LayoutTemplateType.DataLists;
            }

            //prepare localized models
            if (!excludeProperties && entity != null)
            {
                if(entity.IconImageId > 0)
                {
                    model.IconImageModel.Id = entity.IconImageId;
                    model.IconImageModel.ImageUrl = entity.IconImageUrl;
                }
                if(entity.CoverImageId > 0)
                {
                    model.CoverImageModel.Id = entity.CoverImageId;
                    model.CoverImageModel.ImageUrl = entity.CoverImageUrl;
                }

                model.NaviMenuBreadcrumb = _naviMenuService.GetFormattedBreadCrumb(entity);
            }

            //prepare available category templates
            model = PrepareNaviMenuTemplates(model);

            //prepare available parent categories
            model = PrepareParentMenuList(model);

            //prepare languages
            PrepareLanguages(model.AvailableLanguages, model.LanguageCode);
            //prepare model discounts
           //var availableDiscounts = _discountService.GetAllDiscounts(DiscountType.AssignedToCategories, showHidden: true);
           // _discountSupportedModelFactory.PrepareModelDiscounts(model, category, availableDiscounts, excludeProperties);

            //prepare model customer roles
            _aclSupportedModelFactory.PrepareModelCustomerRoles(model, entity, excludeProperties);

            //prepare model stores
            _storeMappingSupportedModelFactory.PrepareModelStores(model, entity, excludeProperties);

            return model;
        }

        /// <summary>
        /// Prepare paged NaviMenu list model
        /// </summary>
        /// <param name="searchModel">NaviMenu search model</param>
        /// <returns>NaviMenu list model</returns>
        public virtual NaviMenuEditListModel PrepareNaviMenuEditListModel(NaviMenuSearchModel searchModel)
        {
            if (searchModel == null)
                throw new ArgumentNullException(nameof(searchModel));
            //get PagedMenus
            var menus = _naviMenuService.GetAllNaviMenusByMenuGroup(menuGrp: searchModel.Group,
                showHidden: true,
                storeId: searchModel.SearchStoreId,
                pageIndex: searchModel.Page - 1, pageSize: searchModel.PageSize,
                lanCode: searchModel.LanguageCode);

            //prepare grid model
            var model = new NaviMenuEditListModel().PrepareToGrid(menus, () =>
            {
                return menus.Select(entity =>
                {
                    //fill in model values from the entity
                    var viewModel = entity.ToModel<NaviMenuEditModel>();

                    //fill in additional values (not existing in the entity)
                    viewModel.NaviMenuBreadcrumb = _naviMenuService.GetFormattedBreadCrumb(entity);
                    viewModel.MenuGroup = ConvertMenuGroupIdToMenuGroup(entity.MenuGroupId);
                    //if (entity.IconImageId > 0)
                    //{
                    //    viewModel.IconImageModel.Id = entity.IconImageId;
                    //    viewModel.IconImageModel.ImageUrl = entity.IconImageUrl;
                    //}
                    //if (entity.CoverImageId > 0)
                    //{
                    //    viewModel.CoverImageModel.Id = entity.CoverImageId;
                    //    viewModel.CoverImageModel.ImageUrl = entity.CoverImageUrl;
                    //}
                    return viewModel;
                });
            });

            return model;
        }

        #endregion
    }
}
